查看原文
其他

写个转盘游戏,被薅羊毛了~

承香墨影 承香墨影 2019-04-15

在 Android 开发者选项中,是可以将“动画程序时长缩放”,设置为 0,这样就可以跳过大部分需要动画的场景,直接出结果。

这个功能在我们调试动画效果的时候非常好用,但是对于已经发布的项目,如果业务严重依赖开始和结束的动画效果,这就明显可以绕过动画时长的限制,无论怎么说也是一种漏洞的存在。

举个例子,猎豹清理大师国际版中,就有一个转盘游戏,可以通过此游戏领取积分,积分积累到达一定数额之后是可以直接提现的。

假如这里可以跳过动画,等于用户可以一直点一直点,这肯定不是正确的产品形态。

ValueAnimator

这里就用 ValueAnimator 来说明,当你在开发者选项中,修改了动画参数之后,其实影响的是 ValueAnimator 中的 sDurationScale 值。

/**
 * Internal constants
*/

private static float sDurationScale = 1.0f;  

这在默认情况下,它的值为 1.0f,在开发者选项中的修改,直接反映在这个值上。

public final boolean doAnimationFrame(long frameTime) {
    if (mStartTime < 0) {
        // First frame. If there is start delay, start delay count down will happen *after* this
        // frame.
        mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
    }
    // ...
    // The frame time might be before the start time during the first frame of
    // an animation.  The "current time" must always be on or after the start
    // time to avoid animating frames at negative time intervals.  In practice, this
    // is very rare and only happens when seeking backwards.
    final long currentTime = Math.max(frameTime, mStartTime);
    boolean finished = animateBasedOnTime(currentTime);

    if (finished) {
        endAnimation();
    }
    return finished;
}

doAnimationFrame() 方法中可以看到,其中会根据 animateBasedOnTime() 方法的返回值来勘定当前动画是否结束,如果结束则调用 endAnimation()

boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        final long scaledDuration = getScaledDuration();
        // ...
        if (scaledDuration == 0) {
            // 0 duration animator, ignore the repeat count and skip to the end
            done = true;
        } // ... 
        // ...
    }
    return done;
}

animateBasedOnTime() 方法中可以看出,只要 getScaledDuration() 返回 0,则表示当前动画已经结束。

private long getScaledDuration() {
    return (long)(mDuration * sDurationScale);
}

这里,只要 sDurationScale 等于 0,任何时候 getScaledDuration() 都会返回 0。

这样就做到了跳过动画的操作。

通过反射解决问题

既然已经知道其中的原理,所以我们只需要想办法保证这个值一直为 1,就好了。

可惜 sDurationScale 是私有的,我们只能通过反射去修改它。

public class AnimatorSettingUtils {
    public static boolean tryOpenAnimator() {
        try {
            Field field = ValueAnimator.class.getDeclaredField("sDurationScale");
            field.setAccessible(true);
            if (field.getFloat(null) != 1) {
                field.setFloat(null1);
            }
        } catch (Exception ignore) {
        }
        return true;
    }
}

此时,在检测到 sDurationScale 的值不为 1 的时候,就将它修改为 1。

到这里就算解决问题了,如果你仔细看过源码,ValueAnimator 中还有一个 areAnimatorsEnabled() 方法,这个 API 是在 Level 26(Android 8.0) 中被加入的,用来判定当前动画是否被开启。

从文档中描述,此方法有两种情况下会返回 false,将动画持续时间设置为 0,或者启动了"省电模式"(并且设置了省点模式下禁用所有动画),则此值会一直返回 false。所以其实并非用户手动设置关闭动画,还有可能在省电模式下自动关闭动画。

因此,如果我们的业务都是依赖动画的执行和结束,我们都需要注意动画是可能提前结束的。在做这样需求的时候,思考一下这样的场景,是否符合我们的功能需要。

END

本文对你有帮助吗?留言、点赞、转发是最大的支持,谢谢!


联机圆桌」👈推荐我的知识星球,一年 50 个优质问题,上桌联机学习。

公众号后台回复成长『成长』,将会得到我准备的学习资料,也能回复『加群』,一起学习进步;你还能回复『提问』,向我发起提问。

推荐阅读:

“寒冬”正是学习时|关于字符编码,你需要知道的都在这里 | 分词,科普及解决方案| 图解:HTTP 范围请求 | 小程序学习资料 |HTTP 内容编码 | 辅助模式实战 | 辅助模式玩出花样 | 小程序 Flex 布局

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存